﻿/*****************************************************************************\
 *             University of Illinois/NCSA Open Source License
 *
 * 
 *      Copyright © 2012,2013, The Board of Trustees of the University of Illinois. All rights reserved.
 *
 *      Developed by: Rakesh Bobba, Derek Dagit, Erich Heine, Denis Kholine,
 *                    Klara Nahrstedt, David Rodgers,   William H. Sanders, Tim Yardley
 *
 *      Information Trust Institute/Coordinated Science Laboratory
 * 
 *      University of Illinois
 *      
 *      The Board of Trustees of the University of Illinois
 *      109 Coble Hall
 *      801 South Wright Street
 *      Champaign, IL 61820-6242
 *
 *      http://www.iti.illinois.edu / http://csl.illinois.edu
 * 
 *      Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 *      associated documentation files (the “Software”), to deal with the Software without restriction, 
 *      including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *      and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 *      subject to the following conditions:
 *
 *      Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *      following disclaimers.
 *      
 *      Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *      the following disclaimers in the documentation and/or other materials provided with the distribution.
 *
 *      Neither the names of Information Trust institute, Coordinated Science Laboratory, University of Illinois,
 *      The Board of Trustees of the University of Illinois nor the names of its contributors may be used to 
 *      endorse or promote products derived from this Software without specific prior written permission.
 *      
 *      THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 *      LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN
 *      NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 *      SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
 *      
 \*****************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using SIEGate.Core.Logging;

namespace SIEGate.Core
{
    public enum LogLevel
    {
        Debug,
        Verbose,
        Info,
        Warn,
        Error,
        Fatal,
        Unexpected
    }

    /// <summary>
    /// The central service for writing to all connected logs.
    /// </summary>
    public static class LogDispatcher
    {
        private class RegisteredListener
        {
            private readonly Filter m_filter;
            private readonly Logger m_logger;

            public RegisteredListener(Filter filter, Logger logger)
            {
                this.m_filter = filter;
                this.m_logger = logger;
            }

            /// <summary>
            /// Check to see if this listener should invoke the logger
            /// for a level, source, tag, and message.
            /// </summary>
            /// <param name="level">level of the logged message</param>
            /// <param name="source">source of the logged message</param>
            /// <param name="tag">tag of the logged message</param>
            /// <param name="message">the logged message</param>
            /// <returns>true if this logger will log for that level, false if not</returns>
            public bool Matches(LogLevel level, string source, string tag, string message)
            {
                return m_filter == null || m_filter(level, source, tag, message);
            }

            // Prefixing with "Listener" so Visual Studio doesn't complain
            // about hiding method from outer class.
            // Since it's a private class, no one needs to worry about the
            // odd name.
            public void ListenerLog(LogLevel level, DateTime time, string source, string tag, string message)
            {
                m_logger.Log(level, time, source, tag, message);
            }

            public void ListenerLogException(LogLevel level, DateTime time, string source, string tag, Exception ex)
            {
                m_logger.LogException(level, time, source, tag, ex);
            }
        }

        static LogDispatcher()
        {
            foreach (LogLevel level in Enum.GetValues(typeof (LogLevel)))
            {
                listeners.Add(level, new List<RegisteredListener>());
            }
        }

        public delegate bool Filter(LogLevel level, string source, string tag, string message);

        // TODO: There's potentially some optimization here by using another 
        // Dictionary or such, but as-written we can use arbitrarily complex 
        // matching.  The cost is logging is an O(n) process.
        private static readonly Dictionary<LogLevel, IList<RegisteredListener>> listeners = new Dictionary<LogLevel, IList<RegisteredListener>>();

        /// <summary>
        /// Add a listener that is invoked on any filter match on any level.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="logger"></param>
        public static void AddAllLevelsListener(Filter filter, Logger logger)
        {
            AddListener(LogLevel.Debug, filter, logger);
        }

        public static void AddListener(LogLevel level, Filter filter, Logger logger)
        {
            foreach (LogLevel existingLogLevel in Enum.GetValues(typeof(LogLevel)).Cast<LogLevel>()
                .Where(existingLogLevel => existingLogLevel.CompareTo(level) >= 0))
            {
                AddExclusiveLevelListener(existingLogLevel, filter, logger);
            }
        }

        public static void AddExclusiveLevelListener(LogLevel level, Filter filter, Logger logger)
        {
            listeners[level].Add(new RegisteredListener(filter, logger));
        }

        public static void Log(LogLevel level, string source, string tag, string message, params object[] formatArgs)
        {
            Log(level, DateTime.UtcNow, source, tag, message, formatArgs);
        }

        // Only string are logged.  A function for logging exceptions does
        // not exist since that would prevent the Filter delegate from
        // being only one delegate, leading to potentially puzzling behavior, 
        // when message or exception was missing, so instead message is
        // required.
        /// <summary>
        /// Log an event.
        /// </summary>
        /// <param name="level">The severity level of the event.</param>
        /// <param name="time">The time of the event in UTC.</param>
        /// <param name="source">Where they event came from.</param>
        /// <param name="tag">Classification of the event.</param>
        /// <param name="message">The information provided by the event.</param>
        /// <param name="formatArgs">Format arguments to pass to string.Format()</param>
        public static void Log(LogLevel level, DateTime time, string source, string tag, string message, params object[] formatArgs)
        {
            message = (formatArgs == null) ? message : string.Format(message, formatArgs);
            foreach (RegisteredListener listener in listeners[level]
                .Where(listener => listener.Matches(level, source, tag, message)))
            {
                listener.ListenerLog(level, DateTime.UtcNow, source, tag, message);
            }
        }

        public static void LogException(LogLevel level, string source, string tag, Exception ex)
        {
            LogException(level, DateTime.UtcNow, source, tag, ex);
        }

        public static void LogException(LogLevel level, DateTime time, string source, string tag, Exception ex)
        {
            foreach (RegisteredListener listener in listeners[level]
                .Where(listener => listener.Matches(level, source, tag, ex.Message)))
            {
                listener.ListenerLogException(level, DateTime.UtcNow, source, tag, ex);
            }
        }

        public static void D(string source, string tag, string message, params object[] formatArgs)
        {
            Log(LogLevel.Debug, source, tag, message, formatArgs);
        }

        public static void E(string source, string tag, string message, params object[] formatArgs)
        {
            Log(LogLevel.Error, source, tag, message, formatArgs);
        }

        public static void E(string source, string tag, Exception ex)
        {
            LogException(LogLevel.Error, source, tag, ex);
        }

        public static void F(string source, string tag, string message, params object[] formatArgs)
        {
            Log(LogLevel.Fatal, source, tag, message, formatArgs);
        }

        public static void F(string source, string tag, Exception ex)
        {
            LogException(LogLevel.Fatal, source, tag, ex);
        }

        public static void I(string source, string tag, string message, params object[] formatArgs)
        {
            Log(LogLevel.Info, source, tag, message, formatArgs);
        }

        public static void V(string source, string tag, string message, params object[] formatArgs)
        {
            Log(LogLevel.Verbose, source, tag, message, formatArgs);
        }

        public static void W(string source, string tag, string message, params object[] formatArgs)
        {
            Log(LogLevel.Warn, source, tag, message, formatArgs);
        }

        public static void U(string source, string tag, string message, params object[] formatArgs)
        {
            Log(LogLevel.Unexpected, source, tag, message, formatArgs);
        }

        public static void U(string source, string tag, Exception ex)
        {
            LogException(LogLevel.Unexpected, source, tag, ex);
        }
    }
}
